/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.modules.serialversion; import java.awt.BorderLayout; import java.lang.reflect.*; import java.io.*; import java.text.MessageFormat; import java.util.*; import org.openide.*; import org.openide.actions.AbstractCompileAction; import org.openide.compiler.Compiler; import org.openide.compiler.CompilerJob; import org.openide.cookies.*; import org.openide.filesystems.FileSystemCapability; import org.openide.loaders.DataFolder; import org.openide.loaders.DataObject; import org.openide.nodes.Node; import org.openide.nodes.NodeAcceptor; import org.openide.src.*; import org.openide.util.*; import org.openide.util.actions.CookieAction; import org.openide.util.enum.*; import org.openide.windows.InputOutput; import org.openide.windows.TopComponent; /** Computes serial version UID for given class, modifies * source element to contain the field. * * @author Jaroslav Tulach, Jesse Glick */ public class SVUIDAction extends CookieAction { /** name of the UID */ private static final String NAME = "serialVersionUID"; /** messages */ private static MessageFormat found = new MessageFormat (getString ("MSG_FOUND_DATA_OBJECT")); private static MessageFormat foundclass = new MessageFormat (getString ("MSG_FOUND_CLASS")); private static MessageFormat modified = new MessageFormat (getString ("MSG_MODIFIED")); private static InputOutput io = null; private static final long serialVersionUID =-2922350967108576270L; public String getName () { return getString ("CTL_SerialVersionUID"); } public HelpCtx getHelpCtx () { return HelpCtx.DEFAULT_HELP; } protected int mode () { return MODE_ALL; } protected Class[] cookieClasses () { return new Class[] { DataObject.class, ClassElement.class }; } protected void performAction (final Node[] nodes) { if (io == null || io.isClosed ()) io = TopManager.getDefault ().getIO (getString ("CTL_IO")); final PrintWriter pw = new PrintWriter (io.getOut ()); final QueueEnumeration queue = new QueueEnumeration (); final Set prompts = new HashSet (); // Set<PromptableItem> RequestProcessor.postRequest (new Runnable () { public void run () { class AlterToCompCookie extends AlterEnumeration { AlterToCompCookie (Enumeration e) { super (e); } public Object alter (Object o) { return ((Node) o).getCookie (CompilerCookie.Compile.class); } } class FilterNulls extends FilterEnumeration { FilterNulls (Enumeration e) { super (e); } public boolean accept (Object o) { return o != null; } } Enumeration enum = new FilterNulls (new AlterToCompCookie (new ArrayEnumeration (nodes))); CompilerJob job = AbstractCompileAction.createJob (enum, Compiler.DEPTH_INFINITE); job.setDisplayName ("Compiling for Serial Version checks"); // [PENDING] if (! job.isUpToDate ()) { pw.println ("Compiling..."); // [PENDING] if (! job.start ().isSuccessful ()) { pw.println ("Compilation failed"); // [PENDING] return; } } else { pw.println ("No need to compile."); // [PENDING] } for (int i = 0; i < nodes.length; i++) { final ClassElement elem = (ClassElement) nodes[i].getCookie (ClassElement.class); if (elem != null) { // check element checkClassElement (elem, pw, prompts); } else { DataObject obj = (DataObject) nodes[i].getCookie (DataObject.class); if (obj != null) { queue.put (obj); } } } checkDataObjects (queue, pw, prompts); pw.println (getString ("MSG_FINISHED")); showPrompts (prompts, pw); } }); } /** Test whether the class element has its SerialVersionUID field * or not. * * @param c class element * @return the element or <code>null</code> if not present */ private static FieldElement find (ClassElement c) { FieldElement f = c.getField (Identifier.create (NAME)); return f; } /** Update the svuid field in a class element. * * @param c class element * @param value the value to set to UID to (removes field if 0) * @param pw writer * @exception SourceException if changes are not allowed */ static void assign (ClassElement c, long value, PrintWriter pw) throws SourceException { FieldElement f = find (c); String initValue = String.valueOf (value) + 'L'; boolean changed; if (value == 0) { if (f != null) { c.removeField (f); changed = true; } else { changed = false; } } else { if (f == null) { f = new FieldElement (); f.setInitValue (initValue); f.setType (Type.LONG); f.setModifiers (Modifier.FINAL | Modifier.STATIC | Modifier.PRIVATE); f.setName (Identifier.create (NAME)); c.addField (f); // find the added field f = find (c); changed = true; } else { if (! initValue.equals (f.getInitValue ())) { f.setInitValue (initValue); changed = true; } else { changed = false; } } } } static void save (ClassElement c, PrintWriter pw) { // Get the data object and thence save capability. DataObject dob = (DataObject) c.getCookie (DataObject.class); if (dob != null) { SaveCookie save = (SaveCookie) dob.getCookie (SaveCookie.class); if (save != null) { pw.println (modified.format (new Object[] { c.getName ().getName (), c.getName ().getFullName () }) ); try { save.save (); } catch (IOException ioe) { ioe.printStackTrace (pw); } } } } /** Checks data objects if it has source cookie and valid serial version * UID. If the data object is folder its content is appended to the * enumeration. Info about processing of objects is written to * given writer. * * @param queue enumeration of data objects * @param writer where to write output * @param prompts a Set<PromptableItem> of prompts to add to */ private static void checkDataObjects (QueueEnumeration queue, PrintWriter writer, Set prompts) { while (queue.hasMoreElements ()) { DataObject obj = (DataObject)queue.nextElement (); if (obj instanceof DataFolder) { // add its content queue.put (((DataFolder)obj).getChildren ()); } else { SourceCookie sc = (SourceCookie)obj.getCookie (SourceCookie.class); if (sc != null) { writer.println ( found.format (new Object[] { obj, obj.getPrimaryFile () }) ); SourceElement elem = sc.getSource (); ClassElement[] arr = elem.getAllClasses (); for (int i = 0; i < arr.length; i++) { checkClassElement (arr[i], writer, prompts); } } } } } /** Checks class element. * @param elem the element to check * @param writer where to print to * @param prompts a Set<PromptableItem> of prompts to add to */ private static void checkClassElement (ClassElement c, PrintWriter pw, Set prompts) { try { String className; SourceElement se = c.getSource (); if (se == null) { className = c.getName ().getFullName (); } else { Identifier p = se.getPackage (); if (p != null) { String pn = p.getFullName (); String r = c.getName ().getFullName ().substring (pn.length () + 1); className = pn + '.' + r.replace ('.', '$'); } else { className = c.getName ().getFullName ().replace ('.', '$'); } } pw.println (foundclass.format (new Object[] { className })); Class clazz = Type.createClass (Identifier.create (className)).toClass (TopManager.getDefault ().currentClassLoader ()); if (clazz.isInterface () || Modifier.isAbstract (clazz.getModifiers ())) { pw.println ("(skipping because interface or abstract)"); // [PENDING] return; } ObjectStreamClass desc = ObjectStreamClass.lookup (clazz); if (desc == null) { pw.println ("(skipping because not serializable)"); // [PENDING] return; } ObjectStreamField[] flds = desc.getFields (); //long currSvuid = desc.getSerialVersionUID (); long currSvuid = 0; try { Field f = clazz.getDeclaredField (NAME); boolean access = f.isAccessible (); try { if (! access) f.setAccessible (true); currSvuid = f.getLong (null); } finally { if (! access) f.setAccessible (false); } } catch (NoSuchFieldException nsfe) { // leave as 0 } catch (Exception e1) { e1.printStackTrace (pw); } long idealSvuid = 0; try { Method m = ObjectStreamClass.class.getDeclaredMethod ("computeSerialVersionUID", new Class[] { Class.class }); boolean access = false; try { access = m.isAccessible (); if (! access) m.setAccessible (true); idealSvuid = ((Long) m.invoke (null, new Object[] { clazz })).longValue (); } finally { if (! access) m.setAccessible (false); } } catch (Exception e2) { e2.printStackTrace (pw); } prompts.add (new PromptableItem (c, className, flds, currSvuid, idealSvuid)); } catch (ClassNotFoundException ex) { ex.printStackTrace (pw); } } /** Show a dialog with all possible modifications. * @param prompts a Set<PromptableItem> */ private static void showPrompts (Set prompts, PrintWriter pw) { if (prompts.size () > 0) { final TopComponent t = new TopComponent (); // [PENDING] should make SerialPrompts extend TopComponent, // and this should override writeReplace to readResolve to null, // and know how to close itself t.setName (getString ("CTL_svuid_dialog")); t.setLayout (new BorderLayout ()); Runnable closer = new Runnable () { public void run () { t.close (); } }; t.add (new SerialPrompts (prompts, pw, closer)); t.open (); } else { pw.println (getString ("MSG_no_prompts")); } } /** Getter for resources. */ private static String getString (String res) { return NbBundle.getBundle (SVUIDAction.class).getString (res); } }